package news

import (
	"HeadLineNews/controller/app/vote"
	"HeadLineNews/controller/manage/category"
	"HeadLineNews/pkg/db/store"
	"HeadLineNews/pkg/snow"
	"HeadLineNews/pkg/zaplog"
	models "HeadLineNews/proto_models/manage/news_model"
	"HeadLineNews/resp"
	"HeadLineNews/utils/gin_ctx"
	"HeadLineNews/utils/transaction"
	"database/sql"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/jmoiron/sqlx"
	"strings"
)

var (
	errNewsHasExists   = errors.New("该新闻已存在")
	errNewsNotExists   = errors.New("该新闻不存在")
	errInvalidAuthorId = errors.New("非法作者id")
)

type Repo interface {
	CheckUniqueNewsName(c *gin.Context, newsTitle string) (err error)
	CheckCategoryExists(c *gin.Context, categoryId string) (err error)
	CheckAuthorsExist(c *gin.Context, authorIdSl []string) (err error)
	InsertNews(c *gin.Context, p *models.RequestAddNews) (err error)

	CheckNewsExists(c *gin.Context, newsId string) (newsTitle string, err error)
	UpdateNews(c *gin.Context, p *models.RequestUpdateNews) (err error)
	RetrieveNews(c *gin.Context) (p *models.ResponseRetrieveNews, err error)
	ListNews(c *gin.Context, p *models.RequestListNews) (data *models.ResponseListNews, err error)
}

type repo struct {
	db *sqlx.DB
}

func NewRepo() Repo {
	return &repo{db: store.DB()}
}

// CheckUniqueNewsName 检查唯一标题名称
func (r *repo) CheckUniqueNewsName(c *gin.Context, newsTitle string) (err error) {
	sqlStr := `SELECT COUNT(*) FROM app__news WHERE news_title=?`
	var count int64
	if err = r.db.Get(&count, sqlStr, newsTitle); err != nil {
		zaplog.L().Error("query news count by title failed", zaplog.Fields(c, newsTitle, err)...)
		resp.ServerBusy(c)
		return
	}

	if count > 0 {
		err = errNewsHasExists
		zaplog.L().Warn("news title has exists", zaplog.Fields(c, newsTitle, nil)...)
		resp.UnprovedParam(c, err.Error())
	}
	return
}

// CheckCategoryExists 检查分类是否存在
func (r *repo) CheckCategoryExists(c *gin.Context, categoryId string) (err error) {
	_, err = category.NewRepo().CheckCategoryExists(c, categoryId)
	return
}

// CheckAuthorsExist 检查作者是否全部存在
func (r *repo) CheckAuthorsExist(c *gin.Context, authorIdSl []string) (err error) {
	sqlStr := `SELECT COUNT(*) FROM app__author WHERE FIND_IN_SET(author_id, ?)`
	var count int
	if err = r.db.Get(&count, sqlStr, strings.Join(authorIdSl, ",")); err != nil {
		zaplog.L().Error("query authors failed", zaplog.Fields(c, authorIdSl, err)...)
		resp.ServerBusy(c)
		return
	}
	if count != len(authorIdSl) {
		err = errInvalidAuthorId
		zaplog.L().Warn("invalid author id", zaplog.Fields(c, authorIdSl, nil)...)
		resp.UnprovedParam(c, errInvalidAuthorId.Error())
		return
	}
	return
}

// InsertNews 将新闻保存进数据库
func (r *repo) InsertNews(c *gin.Context, p *models.RequestAddNews) (err error) {
	if err = transaction.Transaction(r.db, func(tx *sqlx.Tx) (te error) {
		// 保存新闻
		sqlStr := `INSERT INTO app__news(news_id, news_title, news_content, news_category, 
                      create_by, last_update_by, create_time, last_update_time) 
                      VALUES(?,?,?,?,?,?,NOW(),NOW())`
		newsId := snow.GetID()
		if _, te = tx.Exec(sqlStr,
			newsId,
			p.NewsTitle,
			p.NewsContent,
			p.CategoryId,
			c.GetString(gin_ctx.UserId),
			c.GetString(gin_ctx.UserId),
		); te != nil {
			zaplog.L().Error("insert news failed", zaplog.Fields(c, p, te)...)
			return
		}
		// 保存新闻的作者
		var newsAuthorsSl []map[string]interface{}
		for _, authorId := range p.Authors {
			newsAuthorsSl = append(newsAuthorsSl,
				map[string]interface{}{"news_id": newsId, "author_id": authorId},
			)
		}
		sqlStr = `INSERT INTO app__news_authors(news_id, author_id) VALUES(:news_id, :author_id)`
		if _, te = tx.NamedExec(sqlStr, newsAuthorsSl); err != nil {
			zaplog.L().Error("insert new's authors failed", zaplog.Fields(c, newsAuthorsSl, te)...)
			return
		}
		// 缓存新闻的创建时间
		if te = vote.NewCache().CacheNewsCreateTime(newsId); te != nil {
			zaplog.L().Error("cache news create time failed", zaplog.Fields(c, newsId, te)...)
			return
		}
		return
	}); err != nil {
		resp.ServerBusy(c)
		return
	}
	return
}

// CheckNewsExists 根据 id 检查新闻是否存在
func (r *repo) CheckNewsExists(c *gin.Context, newsId string) (newsTitle string, err error) {
	sqlStr := `SELECT news_title FROM app__news WHERE news_id=?`
	if err = r.db.Get(&newsTitle, sqlStr, newsId); err != nil {
		if err == sql.ErrNoRows {
			err = errNewsNotExists
			zaplog.L().Warn("no news was selected by id", zaplog.Fields(c, newsId, nil)...)
			resp.UnprovedParam(c, err.Error())
			return
		}
		zaplog.L().Error("query news by id failed", zaplog.Fields(c, newsId, err)...)
		resp.ServerBusy(c)
		return
	}
	return
}

// UpdateNews 更新新闻
func (r *repo) UpdateNews(c *gin.Context, p *models.RequestUpdateNews) (err error) {
	if err = transaction.Transaction(r.db, func(tx *sqlx.Tx) (te error) {
		// 更新新闻
		query := `UPDATE app__news`
		var args []interface{}
		query += ` SET last_update_time=NOW(),last_update_by=?`
		args = append(args, c.GetString(gin_ctx.UserId))
		if p.NewsTitle != "" {
			query += `,news_title=?`
			args = append(args, p.NewsTitle)
		}
		if p.NewsContent != "" {
			query += `,news_content=?`
			args = append(args, p.NewsContent)
		}
		if p.CategoryId != "" {
			query += `,news_category=?`
			args = append(args, p.CategoryId)
		}
		sqlStr := query + ` WHERE news_id=?`
		args = append(args, p.NewsId)
		if _, te = tx.Exec(sqlStr, args...); err != nil {
			zaplog.L().Error("update news failed", zaplog.Fields(c, p, te)...)
			return
		}
		// 更新作者
		// 删除原来的
		sqlStr = `DELETE FROM app__news_authors WHERE news_id=?`
		if _, te = tx.Exec(sqlStr, p.NewsId); te != nil {
			zaplog.L().Error("delete news authors by newsId failed", zaplog.Fields(c, p.NewsId, te)...)
			return
		}
		//更新最新的作者信息
		var newsAuthorsSl []map[string]interface{}
		for _, authorId := range p.Authors {
			newsAuthorsSl = append(newsAuthorsSl,
				map[string]interface{}{"news_id": p.NewsId, "author_id": authorId},
			)
		}
		sqlStr = `INSERT INTO app__news_authors(news_id, author_id) VALUES(:news_id, :author_id)`
		if _, te = tx.NamedExec(sqlStr, newsAuthorsSl); err != nil {
			zaplog.L().Error("update new's authors failed", zaplog.Fields(c, newsAuthorsSl, te)...)
			return
		}
		return
	}); err != nil {
		resp.ServerBusy(c)
	}

	return
}

// RetrieveNews 检索单条新闻
func (r *repo) RetrieveNews(c *gin.Context) (p *models.ResponseRetrieveNews, err error) {
	newsId := c.Param("newsId")
	p = new(models.ResponseRetrieveNews)
	sqlStr := `SELECT n.news_id, n.news_title, n.news_content, c.category_name AS news_category, 
       		  n.create_time, n.last_update_time,
       	      a1.admin_name AS create_by, a2.admin_name AS last_update_by,
       		  GROUP_CONCAT(CONCAT_WS(';', au.author_id, au.author_name)) AS authors_tmp	
			  FROM app__news n,app__news_category c ,manage__admin a1, manage__admin a2,
			       app__news_authors nas, app__author au
			  WHERE n.news_category=c.category_id
			  AND n.create_by=a1.admin_id
			  AND n.last_update_by=a2.admin_id
			  AND n.news_id=nas.news_id
			  AND nas.author_id=au.author_id
			  AND n.news_id=?`
	if err = r.db.Get(p, sqlStr, newsId); err != nil {
		if strings.Contains(err.Error(), "converting NULL to string is unsupported") {
			err = errNewsNotExists
			zaplog.L().Warn("no news was selected by id", zaplog.Fields(c, newsId, nil)...)
			resp.UnprovedParam(c, err.Error())
			return
		}
		zaplog.L().Error("query news by id failed", zaplog.Fields(c, newsId, err)...)
		resp.ServerBusy(c)
		return
	}
	// 拆分作者
	authorsSl := strings.Split(p.AuthorTmp, ",")
	for _, author := range authorsSl {
		authorSL := strings.Split(author, ";")
		p.Authors = append(p.Authors, &models.ANewsAuthor{
			AuthorId:   authorSL[0],
			AuthorName: authorSL[1],
		})
	}
	p.AuthorTmp = ""
	return
}

// ListNews 返回新闻搜索结果
func (r *repo) ListNews(c *gin.Context, p *models.RequestListNews) (data *models.ResponseListNews, err error) {
	data = new(models.ResponseListNews)
	records := make([]*models.ANews, 0, p.PageSize.Size)
	query := `SELECT n.news_id,n.news_title,c.category_name AS news_category, a.admin_name AS create_by 
			FROM app__news n, app__news_category c, manage__admin a
			WHERE n.news_category=c.category_id
			AND a.create_by=a.admin_id`
	var args []interface{}
	if p.NewsId != "" {
		query += ` AND n.news_id=?`
		args = append(args, p.NewsId)
	}
	if p.NewsTitle != "" {
		query += ` AND n.news_title LIKE CONCAT('%', ?, '%')`
		args = append(args, p.NewsTitle)
	}
	if p.NewsContent != "" {
		query += ` AND n.news_content LIKE CONCAT('%', ?, '%')`
		args = append(args, p.NewsContent)
	}
	if p.CategoryId != "" {
		query += ` AND c.category_id=?`
		args = append(args, p.CategoryId)
	}
	if p.CreateBy != "" {
		query += ` AND a.admin_name=?`
		args = append(args, p.CreateBy)
	}
	if p.TimeRange.TimeRangeStart != "" && p.TimeRange.TimeRangeEnd != "" {
		query += ` AND n.create_time BETWEEN ? AND ?`
		args = append(args, p.TimeRange.TimeRangeStart)
		args = append(args, p.TimeRange.TimeRangeEnd)
	}
	sqlStr := query + ` LIMIT ? OFFSET ?`
	args = append(args, p.PageSize.Size)
	args = append(args, (p.PageSize.Page-1)*p.PageSize.Size)
	if err = r.db.Select(&records, sqlStr, args...); err != nil {
		zaplog.L().Error("query news failed", zaplog.Fields(c, args, err)...)
		resp.ServerBusy(c)
		return
	}
	var count int64
	sqlStr = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS alias", query)
	if err = r.db.Get(&count, sqlStr, args[:len(args)-2]...); err != nil {
		zaplog.L().Error("query new count failed", zaplog.Fields(c, nil, nil)...)
		resp.ServerBusy(c)
	}
	data.Count = count
	data.Records = records
	return
}
